﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Mime;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;
using T.REST;


namespace T.Rest
{
    public class ResRequest
    {
        protected HttpWebRequest _request = null;
        private string _host = null;

        internal Ressource _ressource;

        public ResRequest(Ressource ressource)
        {
            if (ressource.Configuration == null)
                throw new ArgumentException("No configuration set");

            _ressource = ressource;

            _host = ConfigurationManager.AppSettings["host"] ?? ressource.Configuration.Host;
            if (_host.Contains("http"))
                throw new ArgumentException("Host-property should only specify the domain - i.e. www.google.com, not http://www.google.com. Remove protocol from URL");
            if (_host.Contains("localhost"))
                _host = _host.Replace("localhost", Environment.MachineName);

            string protocol = "http://";
            var httpsEnabled = (ConfigurationManager.AppSettings["HttpsEnabled"] ?? "").ToLower().Equals("true");

            if (httpsEnabled && ressource.Configuration.UseSSL)
            {
                protocol = "https://";
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
            }

            string url = protocol + _host + ressource.Configuration.Url;            
            if (url.Contains("?"))
                throw new ArgumentException("Url contains querystring parameters. Use QueryString attribute instead ");

            _request = (HttpWebRequest)WebRequest.Create(url + "?" + ressource.Configuration.QueryString);
            _request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            _request.Accept = "application/json; charset=utf-8";
            _request.Method = ressource.Configuration.Method ?? "GET";
            _request.ContentType = "application/x-www-form-urlencoded";
            _request.UserAgent = "T.Rest_" + DateTime.Now;          
            _request.KeepAlive = false;

            if (!ressource.Configuration.Authorization.Equals(default(KeyValuePair<string, string>)))
            {
                ressource.Configuration.RequestHeader.RemoveAll(x => x.Key.Equals("authorization"));
                ressource.Configuration.RequestHeader.Add(ressource.Configuration.Authorization);
            }

            var parameters = "";
            if(ressource.Configuration.FormsCollection.Count > 0 || ressource.Configuration.RequestHeader.Count > 0)
            {
                if (ressource.Configuration.RequestHeader.Count > 0)
                {
                    foreach (var item in ressource.Configuration.RequestHeader)
                    {
                        if (item.Key.ToLower() != "authorization")
                        {
                            _request.Headers.Add(item.Key, HttpUtility.UrlEncode(item.Value));
                            parameters += "&" + item.Key + "=" + HttpUtility.UrlEncode(item.Value);
                        }
                        else
                        {
                            _request.Headers.Add(item.Key, item.Value);
                            parameters += "&" + item.Key + "=" + item.Value;                            
                        }
                    }
                }

                foreach (string key in ressource.Configuration.FormsCollection)
                    parameters += "&" + key + "=" + ressource.Configuration.FormsCollection[key];
            }

            if (_request.Method != "GET")
            {                                               
                var encoding = new UTF8Encoding();
                var data = encoding.GetBytes(parameters);
                var contentLength = encoding.GetByteCount(parameters);
                _request.ContentLength = contentLength;

                using (var stream = _request.GetRequestStream())
                {
                    stream.Write(data, 0, contentLength);
                    stream.Close();
                }
            }
        }

        /// <summary>
        /// Gets the response from the server without parsing or interpreting the response.
        /// </summary>
        /// <returns></returns>
        public string GetRawResponse()
        {      
            Trace.WriteLine("Executing " + _request.RequestUri.AbsoluteUri);
            string result = null;
            try
            {         
                using (var response = _request.GetResponse())
                {
                    using (var reader = new StreamReader(response.GetResponseStream()))
                        result = reader.ReadToEnd();
                }
            }
            catch (WebException e)  
            {
                //a HTTP 4xx will cause an error in _request.GetResponse() but we really want the response from the server
                try
                {
                    using (var reader = new StreamReader(e.Response.GetResponseStream()))
                        result = reader.ReadToEnd();
                }
                catch(InvalidOperationException ex)
                {
                    _ressource.AssertFail("An error occurred: " + ex.Message);   
                }
            }

            catch (Exception ex)
            {
                _ressource.AssertFail(ex.Message);
            }

            return result;
        }

        /// <summary>
        /// Execute the ressource request. Fire and forget...
        /// </summary>
        public void Execute()
        {
            Trace.WriteLine("Executing " + _request.RequestUri.AbsoluteUri);
            try
            {
                _request.GetResponse();    
            }
            catch(WebException e) { }            
        }

        /// <summary>
        /// Execute the call against the ressource and returns the response header. Useful to i.e. check the HTTP responsecode returned by the server
        /// </summary>
        /// <returns></returns>
        public HttpWebResponse GetResponseHeader()
        {
            Trace.WriteLine("Executing " + _request.RequestUri.AbsoluteUri);
            try
            {
                return (HttpWebResponse)_request.GetResponse();
            }
            catch (WebException e)  
            {
                //a HTTP 4xx will cause an error in _request.GetResponse() but we really want the response from the server
                return  (HttpWebResponse)e.Response;                                     
            }
        }
    }

    public class ResRequest<T> : ResRequest
    {
        public ResRequest(Ressource ressource) : base(ressource)
        {
        }

        /// <summary>
        /// Executes the call against the ressource and parses the result using the provided deserialization function
        /// If REST call does not execute properly AssertFail delegate will be invoked
        /// </summary>
        public T Parse(Func<JavaScriptSerializer, string, T> deserializeFunc)
        {            
            Trace.WriteLine("Executing " + _request.RequestUri.AbsoluteUri);
            T result = default(T);
            try
            {         
                using (var response = _request.GetResponse())
                {
                    using (var reader = new StreamReader(response.GetResponseStream()))
                    {
                        var serializer = new JavaScriptSerializer();
                        string str = reader.ReadToEnd();                        
                        reader.Close();

                        result = deserializeFunc(serializer, str);                        
                    }
                }
            }
            catch (WebException e)  
            {
                //a HTTP 4xx will cause an error in _request.GetResponse() but we really want the response from the server
                try
                {
                    using (var reader = new StreamReader(e.Response.GetResponseStream()))
                    {
                        var serializer = new JavaScriptSerializer();
                        string str = reader.ReadToEnd();
                        reader.Close();                        
                        result = deserializeFunc(serializer, str);
                    }                    
                }
                catch(InvalidOperationException ex)
                {
                    _ressource.AssertFail("Unable to cast JSON: " + ex.Message);   
                }
            }

            catch (Exception ex)
            {
                _ressource.AssertFail(ex.Message);
            }
            
            return result;
        }
    }
}
